package cn.kgc.commons.advice;

import cn.kgc.commons.annoation.MyCacheable;
import cn.kgc.commons.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * <p>添加描述信息</p>
 *
 * @author 卢传琪
 */
@Aspect
@Slf4j
@Component
public class MyCacheableAdvice {

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private RedissonClient redissonClient;

    @Pointcut("@annotation(cn.kgc.commons.annoation.MyCacheable)")
    public void cacheablePointCut() {
    }

    @Around("cacheablePointCut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Method method=((MethodSignature)proceedingJoinPoint.getSignature()).getMethod();
        if (method.isAnnotationPresent(MyCacheable.class)){
            //获取方法上的Mycacheable注解
            MyCacheable myCacheable=method.getAnnotation(MyCacheable.class);
            //获得目标方法上的参数
            DefaultParameterNameDiscoverer defaultParameterNameDiscoverer=new DefaultParameterNameDiscoverer();
            String[] names=defaultParameterNameDiscoverer.getParameterNames(method);
            //获得目标方法上的参数值
            Object [] args=proceedingJoinPoint.getArgs();
            //spEl表达式
            String cacheKeyPress=myCacheable.cacheKey();
            //解析器对象
            ExpressionParser expressionParser=new SpelExpressionParser();
            Expression expression=expressionParser.parseExpression(cacheKeyPress);
            EvaluationContext context=new StandardEvaluationContext();
            //给上下文（EvaluationContext）添加一些参数与参数的值
            for (int i = 0; i < names.length; i++) {
                context.setVariable(names[i],args[i]);
            }
            String cacheKey=myCacheable.cacheName()+":"+expression.getValue(context).toString();
            log.info("cacheKey:{}",cacheKey);
            if (redisUtil.hasKey(cacheKey)){//判断缓存中是否有值
                return redisUtil.get(cacheKey); //如果有值直接返回
            }
            getValue(proceedingJoinPoint, myCacheable, cacheKey);
        }

        return proceedingJoinPoint.proceed();
    }

    private Object getValue(ProceedingJoinPoint proceedingJoinPoint, MyCacheable myCacheable, String cacheKey) throws Throwable {
        //如果没有值，只让一个请求调用目标方法获得结果，将结果放入到缓存中
        String lockKey= cacheKey +":lock";//缓存锁的key
        //获得读写锁
        RReadWriteLock readWriteLock=redissonClient.getReadWriteLock(lockKey);
        RLock rLock=readWriteLock.writeLock();
        rLock.lock();
        try {
            if (redisUtil.hasKey(cacheKey)){//判断缓存中是否有值
                return redisUtil.get(cacheKey); //如果有值直接返回
            }
            Object result= proceedingJoinPoint.proceed();//如果没有值，执行目标方法
            redisUtil.set(cacheKey,result, myCacheable.time(), myCacheable.timeUnit());//将目标方法的返回值放入缓存
            return result;
        } finally {
            rLock.unlock();
        }
    }

}
